// This file is part of WifiCC3000 - the Managed Wifi Driver for the TI CC3000
// WifiCC3000 is free software licensed under the Apache License 2.0
// by Valkyrie-MT

using System;
using Microsoft.SPOT;
using System.Collections;
using System.Text;

namespace Networking
{
    internal static class Command
    {
        internal static byte[] args = new byte[1024];

        internal static int counter = 1;

        internal static ushort GetOpCode(byte[] packet)
        {
            return (ushort)(packet[6] | (packet[7] << 8));
        }

        internal static byte[] Builder(HciCommand OpCode, params object[] arguments)
        {
            return DataBuilder(OpCode, null, null, arguments);
        }

        internal static byte[] DataBuilder(HciCommand OpCode, byte[] data, byte[] extraData, params object[] arguments)
        {
            data = data ?? new byte[0];
            extraData = extraData ?? new byte[0];

            int pos = 0;

            foreach (var anArg in arguments)
            {
                if (anArg is byte[])
                {
                    Command.args.Overwrite(pos, anArg as byte[]);
                    pos += (anArg as byte[]).Length;
                }
                else if (anArg is UInt32[]) // Because of a bug in .NET MF, the check for UInt32[] must be up here: http://netmf.codeplex.com/workitem/2120
                {
                    var arr = (UInt32[])anArg;

                    foreach (UInt32 num in arr)
                    {
                        Command.args[pos++] = (byte)(num >> 0);
                        Command.args[pos++] = (byte)(num >> 8);
                        Command.args[pos++] = (byte)(num >> 16);
                        Command.args[pos++] = (byte)(num >> 24);
                    }
                }
                else if (anArg is string)
                {
                    Command.args.Overwrite(pos, Encoding.UTF8.GetBytes(anArg as string));
                    pos += (anArg as string).Length;
                }
                else if (anArg is UInt32)
                {
                    var num = (UInt32)anArg;
                    Command.args[pos++] = (byte)(num >> 0);
                    Command.args[pos++] = (byte)(num >> 8);
                    Command.args[pos++] = (byte)(num >> 16);
                    Command.args[pos++] = (byte)(num >> 24);
                }
                else if (anArg is Int32)
                {
                    var num = (Int32)anArg;
                    Command.args[pos++] = (byte)(num >> 0);
                    Command.args[pos++] = (byte)(num >> 8);
                    Command.args[pos++] = (byte)(num >> 16);
                    Command.args[pos++] = (byte)(num >> 24);
                }
                else if (anArg is UInt16)
                {
                    var num = (UInt16)anArg;
                    Command.args[pos++] = (byte)(num >> 0);
                    Command.args[pos++] = (byte)(num >> 8);
                }
                else if (anArg is Int16)
                {
                    var num = (Int16)anArg;
                    Command.args[pos++] = (byte)(num >> 0);
                    Command.args[pos++] = (byte)(num >> 8);
                }
                else if (anArg is Byte)
                {
                    Command.args[pos++] = (byte)anArg;
                }
                else
                {
                    Debugger.Print("Warning: Argument could not be converted to bytes -- " + anArg.ToString());
                }

            }

            var hciType = ((uint)OpCode & 0x00F0) == 0x0080 || OpCode == HciCommand.HCI_CMND_NVMEM_WRITE ? HciCommandType.HCI_TYPE_DATA : HciCommandType.HCI_TYPE_CMND;

            var headerSize = hciType == HciCommandType.HCI_TYPE_CMND ? Constants.HEADERS_SIZE_CMD : Constants.SPI_HEADER_SIZE + Constants.SIMPLE_LINK_HCI_DATA_CMND_HEADER_SIZE;

            //var argLength = System.Math.Max(LookupArgLength(c.OpCode), c.args.Length);  //TODO: the LookupArLength method is obsolete I think...  just use c.args.Length
            var argLength = pos;  //TODO: the LookupArLength method is obsolete I think...  just use c.args.Length

            var paddedLength = (headerSize + data.Length + extraData.Length + argLength) % 2 == 0 ? (headerSize + data.Length + extraData.Length + argLength) : (headerSize + data.Length + extraData.Length + argLength + 1); // must be an even number

            // Construct the byte array for this command

            byte[] commandbytes;

            commandbytes = new byte[paddedLength];

            commandbytes[0] = 0x01;  // Write
            commandbytes[1] = (byte)(((paddedLength - Constants.SPI_HEADER_SIZE) & 0xFF00) >> 8);  // message size MSB
            commandbytes[2] = (byte)((paddedLength - Constants.SPI_HEADER_SIZE) & 0x00FF);         // message size LSB
            commandbytes[5] = (byte)hciType; // HCI Message Type
            commandbytes[6] = (byte)((short)OpCode & 0x00FF);  //opcode LSB


            if (hciType == HciCommandType.HCI_TYPE_CMND)
            {
                commandbytes[7] = (byte)(((short)OpCode & 0xFF00) >> 8);        // opcode MSB
                // Commands seem to use a single byte arg length ... why? why TI?  Was that 1 byte really that important?????
                commandbytes[8] = (byte)argLength;

                commandbytes.Overwrite(headerSize, args, pos);
            }
            else
            {
                commandbytes[7] = (byte)argLength; // 1-byte Arg Length

                commandbytes[8] = (byte)((short)(argLength + data.Length + extraData.Length) & 0x00FF);  // arg and data length LSB
                commandbytes[9] = (byte)(((short)(argLength + data.Length + extraData.Length) & 0xFF00) >> 8);  // arg and data length MSB

                commandbytes.Overwrite(headerSize, args, pos);  // write the arg bytes
                if (data.Length > 0)
                {
                    commandbytes.Overwrite(headerSize + pos, data, data.Length);  // write the data bytes
                    if (extraData.Length > 0) commandbytes.Overwrite(headerSize + pos + data.Length, extraData, extraData.Length); // write the extra data
                }
            }

            //Debugger.Print(OpCode.ToString() + ": ");

            //foreach (var aByte in commandbytes)
            //    Debugger.Print("0x" + aByte.ToHexString() + ", ");

            //Debugger.Print("Message Constructed for " + OpCode.ToString() + ": " + commandbytes.ToHexString());


            return commandbytes;
        }

        internal static byte[] PatchBuilder(PatchType patchType)
        {
            // Construct the byte array for this command

            byte[] commandbytes;

            commandbytes = new byte[12];

            commandbytes[0] = 0x01;   // Write
            commandbytes[1] = 0x00;   // message size MSB
            commandbytes[2] = 0x07;   // message size LSB
            commandbytes[5] = (byte)HciCommandType.HCI_TYPE_PATCH; // HCI Message Type
            commandbytes[6] = (byte)patchType;  //opcode LSB
            commandbytes[7] = 0x02;
            commandbytes[8] = 0x00;
            commandbytes[9] = 0x00;
            commandbytes[10] = 0x00;
            commandbytes[11] = 0xFF;  /// garbage/padding byte?

            return commandbytes;
        }

    }


    //class Command
    //{

    //    public HciCommand OpCode { get; set; }
    //    byte[] args = new byte[0];

    //    public static byte[] messageArray = null;

    //    internal Command(HciCommand OpCode) : this(OpCode, new byte[0]) { }

    //    internal Command(HciCommand OpCode, byte[] arguments)
    //    {
    //        this.OpCode = OpCode;
    //        args = arguments ?? new byte[0];
    //    }

    //    /// <summary>
    //    /// Automatically convert argument types to the appropriate byte representation 
    //    /// Note: if the spec requires a 4 byte representation for a particular argument, be sure to pass in a 4-byte representation, such as an integer (4-bytes), not a short (2-bytes)
    //    /// </summary>
    //    /// <param name="OpCode"></param>
    //    /// <param name="arguments"></param>
    //    internal Command(HciCommand OpCode, params object[] arguments)
    //    {
    //        //Debugger.Print("Command Construct Mem: " + Debug.GC(true));
            
    //        this.OpCode = OpCode;

    //        // compile the argument objects into a byte array
    //        var allArgs = new ArrayList();


    //            foreach (var anArg in arguments)
    //            {
    //                if (anArg is byte[])
    //                    allArgs.AddRange((byte[])anArg);
    //                else if (anArg is string)
    //                    allArgs.AddRange(Encoding.UTF8.GetBytes(anArg as string));
    //                else if (anArg is UInt32)
    //                    allArgs.AddRange(((UInt32)anArg).ToBytesRev());
    //                else if (anArg is UInt16)
    //                    allArgs.AddRange(((UInt16)anArg).ToBytesRev());
    //                else if (anArg is Int32)
    //                    allArgs.AddRange(((Int32)anArg).ToBytesRev());
    //                else if (anArg is Int16)
    //                    allArgs.AddRange(((Int16)anArg).ToBytesRev());
    //                else if (anArg is Byte)
    //                    allArgs.Add((Byte)anArg);
    //                else
    //                    Debugger.Print("Warning: Argument could not be converted to bytes -- " + anArg.ToString());
    //            }
            

    //        args = (byte[])allArgs.ToArray(typeof(byte));

    //            // I was having some out of memory exceptions, so I am adding some unnecessary lines here to help the GC?  
    //            //allArgs.Clear();
            
    //    }


    //    public static implicit operator byte[](Command c)
    //    {
    //        var hciType = ((uint)c.OpCode & 0x00F0) == 0x0080 ? HciCommandType.HCI_TYPE_DATA : HciCommandType.HCI_TYPE_CMND;
    //        var headerSize = hciType == HciCommandType.HCI_TYPE_CMND ? Constants.HEADERS_SIZE_CMD : Constants.SPI_HEADER_SIZE + Constants.SIMPLE_LINK_HCI_DATA_CMND_HEADER_SIZE;

    //        //var argLength = System.Math.Max(LookupArgLength(c.OpCode), c.args.Length);  //TODO: the LookupArLength method is obsolete I think...  just use c.args.Length
    //        var argLength = c.args.Length;  //TODO: the LookupArLength method is obsolete I think...  just use c.args.Length

    //        var paddedLength = (headerSize + argLength) % 2 == 0 ? (headerSize + argLength) : (headerSize + argLength + 1); // must be an even number

    //        // Construct the byte array for this command

    //        byte[] commandbytes;

    //        commandbytes = new byte[paddedLength];

    //        commandbytes[0] = 0x01;  // Write
    //        commandbytes[1] = (byte)(((paddedLength - Constants.SPI_HEADER_SIZE) & 0xFF00) >> 8);  // message size MSB
    //        commandbytes[2] = (byte)((paddedLength - Constants.SPI_HEADER_SIZE) & 0x00FF);         // message size LSB
    //        commandbytes[5] = (byte)hciType; // HCI Message Type
    //        commandbytes[6] = (byte)((short)c.OpCode & 0x00FF);  //opcode LSB
    //        commandbytes[7] = (byte)(((short)c.OpCode & 0xFF00) >> 8);        // opcode MSB

    //        if (hciType == HciCommandType.HCI_TYPE_CMND)
    //        {
    //            // Commands seem to use a single byte arg length ... why? why TI?  Was that 1 byte really that important?????
    //            commandbytes[8] = (byte)argLength;
    //        }
    //        else
    //        {
    //            // Data seems to use a 2 byte (short) arg length
    //            commandbytes[8] = (byte)((short)argLength & 0x00FF);  //opcode LSB
    //            commandbytes[9] = (byte)(((short)argLength & 0xFF00) >> 8);        // opcode MSB
    //        }

    //        commandbytes.Overwrite(headerSize, c.args);

    //        return commandbytes;
    //    }

    //    //private static int LookupArgLength(HciCommand hciCommand)
    //    //{
    //    //    // Ugh, Seriously TI!?  There are different required argument lengths for different commands!  

    //    //    switch (hciCommand)
    //    //    {
    //    //        case HciCommand.HCI_CMND_SIMPLE_LINK_START: return Constants.WLAN_SL_INIT_START_PARAMS_LEN;
    //    //        case HciCommand.HCI_CMND_EVENT_MASK: return Constants.WLAN_SET_MASK_PARAMS_LEN;


    //    //        default: return 0;
    //    //    }


    //    //}

    //}
}
